#include "connectPool.h"

using namespace std;

/* 提供一个静态方法，让外部调用唯一实例 */
ConnectPool *ConnectPool::getConnectPool()
{
    static ConnectPool pool;
    return &pool;
}

/* 析构函数 */
ConnectPool::~ConnectPool()
{
    /* todo... */
}

/* 从连接池中取出一个MySql连接 */
std::shared_ptr<MysqlDataBase> ConnectPool::getConnection()
{
    /* 得到的MySqlDataBase对象一定是已经连接上服务器的. */

    /*
        1.判断连接池中是否有可用的连接
        2.如果没有，就条件等待
    */
    /* 加锁 */
    std::unique_lock<mutex> locker(m_mutex);
    while (m_connectQueue.empty())
    {
        /* 等待条件变量 */
        if (std::cv_status::timeout == m_cond_consume.wait_for(locker, std::chrono::milliseconds(500)))
        {
            /* 继续等待 */
            if (m_connectQueue.empty())
            {
                continue;
            }
        }
    }

    /* 程序到了这个地方:数据库连接池有可用的连接 */
    std::shared_ptr<MysqlDataBase> connPtr(m_connectQueue.front(), [this](MysqlDataBase *conn)
                                           {
                                               /* 共享资源 */
                                               m_mutex.lock();
                                               /* 刷新时间 */
                                               conn->refreshAliveTime();
                                               /* 添加到连接池中 */
                                               m_connectQueue.push(conn);
                                               /* 解锁 */
                                               m_mutex.unlock(); });

    /* 取出队头元素 */
    m_connectQueue.pop();
    /* 唤醒生产者 */
    m_cond_produce.notify_all();

    return connPtr;
}

/* 解析配置文件 */
bool ConnectPool::parseConfigFile(const std::string &path)
{
    ifstream ifs(path);
    if (ifs.is_open() == false)
    {
        cout << "failed to open config file." << endl;
        return false;
    }

    Json::Reader rd;
    Json::Value root;

    if (rd.parse(ifs, root) == false)
    {
        cout << "json parse failed." << endl;
        return false;
    }

    m_hostname = root["hostname"].asString();
    m_username = root["username"].asString();
    m_passwd = root["passwd"].asString();
    m_port = root["port"].asInt();
    m_dbname = root["dbname"].asString();
    m_minSize = root["minSize"].asInt();
    m_maxSize = root["maxSize"].asInt();
    m_maxIdleTime = root["maxIdleTime"].asInt();

    return true;
}
/* 添加连接到连接池中 */
bool ConnectPool::addConnection()
{
    MysqlDataBase *conn = new MysqlDataBase(m_hostname, m_username, m_passwd);

    /* 对每一个连接上数据库的连接打上时间戳 */
    conn->refreshAliveTime();

    /* 连接数据库 */
    conn->connectDB(m_dbname);

    /* 添加连接到连接池 */
    m_connectQueue.push(conn);

    return true;
}

/* 生产者线程入口 - 向数据库连接池生产连接句柄 */
void ConnectPool::produceConnection()
{
    while (true)
    {
        /* unique_lock 智能锁 */
        unique_lock<mutex> locker(m_mutex);
        while (m_connectQueue.size() >= m_minSize)
        {
            /* 等待条件变量 */
            m_cond_produce.wait(locker);
        }

        /* 添加连接到连接池 */
        addConnection();

        /* 通知给消费者 */
        m_cond_consume.notify_all();
    }
}

/* 回收线程 - 回收数据库连接池的连接 */
void ConnectPool::recycleConnection()
{
    while (true)
    {
        /* 休眠1s. */
        this_thread::sleep_for(std::chrono::seconds(1));
        /* 加锁 */
        std::unique_lock<mutex> locker(m_mutex);
        /* 连接池的连接句柄数必须大与最小值 */
        while (m_connectQueue.size() > m_minSize)
        {
            /* 查看队头 */
            MysqlDataBase *conn = m_connectQueue.front();

            /* 存活时间 = 当前时间 - 上次操作时间 */
            if (conn->getAliveTime() > m_maxIdleTime)
            {
                m_connectQueue.pop();
                delete conn;
                conn = nullptr;
            }
        }
    }
}

/* 构造函数私有化 */
ConnectPool::ConnectPool()
{
    /* 解析JSON配置文件 */
    if (parseConfigFile("./config.json") == false)
    {
        return;
    }

    /* 将连接添加到连接池中 */
    for (int idx = 0; idx < m_minSize; idx++)
    {
        addConnection();
    }

    /* 生产者 */
    thread produce(&ConnectPool::produceConnection, this);

    /* 回收者 */
    thread recycle(&ConnectPool::recycleConnection, this);

    /* 线程分离 */
    produce.detach();
    recycle.detach();
}
